package org.hawk.gwt.ppc.parser.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.hawk.gwt.ppc.parser.AnnotationDescriptor;
import org.hawk.gwt.ppc.parser.BlockDescriptor;
import org.hawk.gwt.ppc.parser.ClassDescriptor;
import org.hawk.gwt.ppc.parser.ConstructorDescriptor;
import org.hawk.gwt.ppc.parser.Descriptor;
import org.hawk.gwt.ppc.parser.ExpressionDescriptor;
import org.hawk.gwt.ppc.parser.FieldDescriptor;
import org.hawk.gwt.ppc.parser.JavaKeywords;
import org.hawk.gwt.ppc.parser.MethodDescriptor;
import org.hawk.gwt.ppc.parser.PreprocessorParseException;
import org.hawk.gwt.ppc.parser.Token;
import org.hawk.gwt.ppc.parser.VariableDescriptor;
import org.hawk.gwt.ppc.parser.impl.VariableDescriptorParser.VariableDescriptorImpl;
import org.hawk.gwt.ppc.utils.PreprocessorException;

import static org.hawk.gwt.ppc.parser.impl.DescriptorParserUtils.*;

/**
 * Utilities to help to parse class members.
 * 
 * @author alex.bereznevatiy@gmail.com
 *
 */
final class MemberUtils {
	private MemberUtils() {}
	
	/**
	 * Parses single member and stores it to the correct parameter.
	 * @param tokenizer
	 * @param parser
	 * @param methods
	 * @param initilizers
	 * @param constructors
	 * @param declaredClasses
	 * @param fields
	 * @param children
	 * @throws PreprocessorException in case of troubles
	 */
	static void parseClassMember(Tokenizer tokenizer,
			TokenBasedJavaParser parser,
			List<MethodDescriptor> methods,
			List<BlockDescriptor> initilizers,
			List<ConstructorDescriptor> constructors,
			List<ClassDescriptor> declaredClasses, 
			List<FieldDescriptor> fields, 
			List<Descriptor> children) throws PreprocessorException {
		
		List<String> modifiersList = new ArrayList<String>(2);
		List<AnnotationDescriptor> annotations = new ArrayList<AnnotationDescriptor>(1);
		
		if (AnnotationDescriptorParser.parseAnnotations(parser, tokenizer, annotations)) {
			declaredClasses.add((ClassDescriptor) DescriptorType.CLASS.parse(
					parser, tokenizer, modifiersList, JavaKeywords.ANNOTATION));
			return;
		}
		
		readModifiers(modifiersList, tokenizer);
		
		parsePureClassMember(tokenizer, parser, methods, initilizers,
				constructors, declaredClasses, fields, modifiersList, annotations);
	}

	private static void parsePureClassMember(Tokenizer tokenizer,
			TokenBasedJavaParser parser, List<MethodDescriptor> methods,
			List<BlockDescriptor> initilizers,
			List<ConstructorDescriptor> constructors,
			List<ClassDescriptor> declaredClasses,
			List<FieldDescriptor> fields, 
			List<String> modifiersList,
			List<AnnotationDescriptor> annotations)
			throws PreprocessorException, PreprocessorParseException {
		
		if (DescriptorType.CLASS.accepts(tokenizer.current())) {
			declaredClasses.add((ClassDescriptor) DescriptorType.CLASS.parse(
					parser, tokenizer, modifiersList));
			return;
		}
		
		if (DescriptorType.BLOCK.accepts(tokenizer.current())) {
			initilizers.add((BlockDescriptor) DescriptorType.BLOCK.parse(
					parser, tokenizer, modifiersList));
			return;
		}
		
		parseNamedClassMember(tokenizer, parser, methods, constructors,
				fields, annotations, modifiersList);
	}

	private static void parseNamedClassMember(Tokenizer tokenizer,
			TokenBasedJavaParser parser, List<MethodDescriptor> methods,
			List<ConstructorDescriptor> constructors,
			List<FieldDescriptor> fields, 
			List<AnnotationDescriptor> annotations,
			List<String> modifiersList)
			throws PreprocessorException, PreprocessorParseException {
		
		StringBuilder typeParams = new StringBuilder();
		StringBuilder typeName = new StringBuilder();
		readTypeParameters(typeParams, tokenizer);
		readTypeName(typeName, tokenizer);
		
		if (tokenizer.current().equalsTo('(')) {
			parseConstructor(constructors, parser, tokenizer, modifiersList, 
					annotations, typeName.toString(), typeParams.toString());
			return;
		}
		
		String name = getMemberName(tokenizer);
		Token tok = tokenizer.current();
		
		if (tok.equalsTo(';') || tok.equalsTo('=')) {
			parseField(parser, tokenizer, fields, typeName.toString(), 
					typeParams.toString(), name, modifiersList, annotations);
			return;
		}
		
		parseMethod(parser, tokenizer, methods, typeName.toString(), typeParams.toString(), name, modifiersList, annotations);
	}

	private static void parseField(TokenBasedJavaParser parser, Tokenizer tokenizer,
			List<FieldDescriptor> fields, String type, String typeParams,
			String name, List<String> modifiersList, List<AnnotationDescriptor> annotations) 
					throws PreprocessorException {
		if (typeParams.length() > 0) {
			throw new PreprocessorParseException(tokenizer.current());
		}
		if (tokenizer.current().equalsTo(';')) {
			fields.add(new FieldDescriptorImpl(name, type, annotations, modifiersList, null));
			lookupNext(tokenizer, false);
			return;
		}

		if (!tokenizer.current().equalsTo('=')) {
			throw new PreprocessorParseException(tokenizer.current());
		}
		
		lookupNext(tokenizer, false);
		
		fields.add(new FieldDescriptorImpl(name, type, annotations, modifiersList, 
				(ExpressionDescriptor) DescriptorType.LINE_EXPRESSION.parse(parser, tokenizer)));
		lookupNext(tokenizer, false);
	}

	private static String getMemberName(Tokenizer tokenizer)
			throws PreprocessorParseException, PreprocessorException {
		Token tok = tokenizer.current();
		if (!tok.isWord()) {
			throw new PreprocessorParseException("Expected member name", tokenizer.current());
		}
		
		lookupNext(tokenizer, false);
		
		return tok.toString();
	}

	private static void parseConstructor(
			List<ConstructorDescriptor> constructors, TokenBasedJavaParser parser, Tokenizer tokenizer,
			List<String> modifiersList, List<AnnotationDescriptor> annotations, String name, String typeParams) 
					throws PreprocessorException {
		if (typeParams.length() > 0) {
			throw new PreprocessorParseException("Constructor cannot have a type parameters", tokenizer.current());
		}
		
		List<VariableDescriptor> parameters = new ArrayList<VariableDescriptor>(2);
		
		boolean isVarArgs = parseParameters(parser, tokenizer, parameters);
		
		List<String> exceptions = readThrowsSection(parser, tokenizer);
		
		BlockDescriptor body = (BlockDescriptor) DescriptorType.BLOCK.parse(parser, tokenizer);
		
		constructors.add(new ConstructorDescriptorImpl(body, modifiersList, 
				annotations, parameters, exceptions, new ArrayList<Descriptor>(), isVarArgs));
	}
	
	private static boolean parseParameters(TokenBasedJavaParser parser, 
			Tokenizer tokenizer, List<VariableDescriptor> parameters) throws PreprocessorException {
		lookupNext(tokenizer, false);
		while (!tokenizer.current().equalsTo(')')) {
			parameters.add((VariableDescriptor) DescriptorType.VARIABLE.parse(parser, tokenizer));
			if (tokenizer.current().equalsTo(',')) {
				lookupNext(tokenizer, false);
			}
		}
		lookupNext(tokenizer, false);
		return !parameters.isEmpty() && ((VariableDescriptorImpl)parameters.get(parameters.size() - 1)).isVarArg();
	}

	private static List<String> readThrowsSection(TokenBasedJavaParser parser,
			Tokenizer tokenizer) throws PreprocessorException {
		if (!tokenizer.current().equalsTo(JavaKeywords.THROWS)) {
			return Collections.emptyList();
		}
		List<String> result = new ArrayList<String>(2);
		while (!tokenizer.current().equalsTo('{')) {
			lookupNext(tokenizer, false);
			StringBuilder type = new StringBuilder();
			readTypeName(type, tokenizer);
			result.add(type.toString());
		}
		
		return result;
	}

	private static void parseMethod(TokenBasedJavaParser parser,
			Tokenizer tokenizer, 
			List<MethodDescriptor> methods, 
			String returnType,
			String typeParameters, 
			String name, 
			List<String> modifiersList,
			List<AnnotationDescriptor> annotations) 
				throws PreprocessorException {
		List<VariableDescriptor> parameters = new ArrayList<VariableDescriptor>(2);
		
		boolean isVarArgs = parseParameters(parser, tokenizer, parameters);
		
		if (tokenizer.current().equalsTo(JavaKeywords.DEFAULT)) {
			lookupNext(tokenizer, false);
			
			if (!parameters.isEmpty()) {
				throw new PreprocessorParseException(tokenizer.current());
			}
			
			ExpressionDescriptor defaultExpression = (ExpressionDescriptor) DescriptorType.LINE_EXPRESSION.parse(parser, tokenizer);
			
			methods.add(new MethodDescriptorImpl(name, returnType, 
					typeParameters, modifiersList, null, defaultExpression, 
					annotations, parameters, new ArrayList<String>(1), 
					new ArrayList<Descriptor>(), isVarArgs));
			return; // annotation parameter
		}
			
		List<String> exceptions = readThrowsSection(parser, tokenizer);
		
		if (tokenizer.current().equalsTo(';')) {
			methods.add(new MethodDescriptorImpl(name, returnType, 
					typeParameters, modifiersList, null, null, 
					annotations, parameters, exceptions, 
					new ArrayList<Descriptor>(), isVarArgs));
			lookupNext(tokenizer, false);
			return; // abstract method
		}
		
		BlockDescriptor body = (BlockDescriptor) DescriptorType.BLOCK.parse(parser, tokenizer);
		
		methods.add(new MethodDescriptorImpl(name, returnType, 
				typeParameters, modifiersList, body, null, 
				annotations, parameters, exceptions, 
				new ArrayList<Descriptor>(), isVarArgs));
	}
}
